<!--
 * @Description: 表格操作按钮
 * @Author: HuiSir<273250950@qq.com>
 * @Date: 2021-12-16 16:56:44
 * @LastEditTime: 2025-09-28 11:02:19
-->
<template>
  <div class="row-btns">
    <!-- 插槽可在左侧插入新按钮 -->
    <slot name="left" :row="rowData" />
    <!-- 默认左侧插槽 -->
    <slot :row="rowData" />
    <el-button
      v-if="contains.includes('detail')"
      type="primary"
      link
      size="small"
      @click="handleDetail"
    >
      <fy-icon name="ChatLineSquare" size="13" />
      详情
    </el-button>
    <el-button
      v-if="contains.includes('edit')"
      type="primary"
      link
      size="small"
      @click="handleEdit('edit')"
    >
      <fy-icon name="edit" size="13" />
      编辑
    </el-button>
    <el-button
      v-if="contains.includes('add')"
      type="primary"
      link
      size="small"
      @click="handleEdit('add')"
    >
      <fy-icon name="plus" size="13" />
      新增
    </el-button>
    <el-popconfirm
      v-if="contains.includes('del')"
      confirm-button-text="确认"
      cancel-button-text="取消"
      title="确认删除此行?"
      :hide-after="0"
      @confirm="handleDelete"
    >
      <template #reference>
        <el-button type="danger" link size="small" :loading="deleteLoading">
          <fy-icon v-if="!deleteLoading" name="delete" size="13" />
          删除
        </el-button>
      </template>
    </el-popconfirm>
    <!-- 插槽可在右侧插入新按钮 -->
    <slot name="right" :row="rowData" />
    <!-- 详情弹窗 -->
    <fy-detail-dialog
      v-if="contains.includes('detail')"
      v-model="showDetailDialog"
      :loading="dialogLoading"
      :params="currDetailData"
      :title="detailDialogTitle"
      :options="detailOptions"
      top="15%"
    >
      <!-- 详情弹窗插槽 -->
      <template
        v-for="item in detailOptions.filter((o) => o.slot)"
        :key="item.key"
        v-slot:[item.slot]="scope"
      >
        <slot :name="item.slot" v-bind="scope" />
      </template>
    </fy-detail-dialog>
    <!-- 编辑和新增弹窗 -->
    <fy-form-dialog
      v-if="contains.includes('edit') || contains.includes('add')"
      v-model="showEditDialog"
      v-model:options="currEditDialogOptions"
      v-bind="currDialogProps"
      :loading="dialogLoading"
      :params="currEditData"
      @submit="handleEditSubmit"
      @upload:change="bindUploadChange"
    >
      <!-- 编辑弹窗表单插槽 -->
      <template
        v-for="item in currEditDialogSlotOptions"
        :key="item.key"
        v-slot:[item.slot]="scope"
      >
        <slot :name="item.slot" v-bind="scope" />
      </template>
    </fy-form-dialog>
  </div>
</template>
<script lang="ts" setup>
/* eslint-disable @typescript-eslint/no-explicit-any */
import { ref, PropType, Ref } from "vue";
import {
  IFormOption,
  IDetailOption,
  SubmitCallbacks,
  DeleteCallbacks,
  Awaitable,
  UploadEventParams,
  UploadChangeForm,
  IFormDialogProps,
} from "../types";
import { deepCopy } from "../../helpers";

const props = defineProps({
  /**
   * 按钮展示设置
   */
  contains: {
    type: Array as PropType<("edit" | "detail" | "add" | "del")[]>,
    default: () => ["edit", "del"],
  },
  /**
   * 行数据，从表格行传入,这里不需要响应式，不允许更改
   */
  rowData: {
    type: Object as PropType<Record<string, unknown>>,
    required: true,
  },
  /**
   * 详情弹窗标题
   */
  detailDialogTitle: {
    type: String,
    default: "详情",
  },
  /**
   * 新增弹窗固定参数
   */
  addDialogProps: {
    type: Object as PropType<IFormDialogProps>,
    default: () => ({}),
  },
  /**
   * 编辑弹窗固定参数
   */
  editDialogProps: {
    type: Object as PropType<IFormDialogProps>,
    default: () => ({}),
  },
  /**
   * 详情弹窗弹出前钩子，用于处理表单默认展示的数据
   */
  beforeShowDetailDialog: {
    type: Function as PropType<
      (
        rowData: Ref<Record<string, any>>,
        detailOptions: Ref<IDetailOption[]>
      ) => Awaitable<void>
    >,
  },
  /**
   * 编辑弹窗弹出前钩子，用于处理表单默认展示的数据
   */
  beforeShowEditDialog: {
    type: Function as PropType<
      (
        rowData: Ref<Record<string, any>>,
        editFormOptions: Ref<IFormOption[]>
      ) => Awaitable<void>
    >,
  },
  /**
   * 新增弹窗弹出前钩子，用于处理表单默认展示的数据
   */
  beforeShowAddDialog: {
    type: Function as PropType<
      (
        rowData: Ref<Record<string, any>>,
        addFormOptions: Ref<IFormOption[]>
      ) => Awaitable<void>
    >,
  },
  /**
   * 文件上传change事件
   */
  uploadChange: {
    type: Function as PropType<
      (uploadParams: UploadEventParams, form: UploadChangeForm) => void
    >,
  },
});

/**
 * 详情弹窗表单配置
 */
const detailOptions = defineModel("detailOptions", {
  type: Array as PropType<IDetailOption[]>,
  default: () => [],
});
/**
 * 新增弹窗表单配置
 */
const addFormOptions = defineModel("addFormOptions", {
  type: Array as PropType<IFormOption[]>,
  default: () => [],
});
/**
 * 编辑弹窗表单配置
 */
const editFormOptions = defineModel("editFormOptions", {
  type: Array as PropType<IFormOption[]>,
  default: () => [],
});

const emit = defineEmits<{
  // 元组语法：明确参数名称和类型
  submit: [
    params: Record<string, unknown>,
    callbacks: SubmitCallbacks, // 对象参数
    btn?: "edit" | "add"
  ];
  delete: [rowData: Record<string, unknown>, callbacks: DeleteCallbacks];
}>();

// 弹窗loading
const dialogLoading = ref(false);
// 删除按钮loading
const deleteLoading = ref(false);
// 显隐编辑窗
const showEditDialog = ref(false);
// 弹窗固定参数
const currDialogProps = ref<IFormDialogProps>({});
// 编辑弹窗表单配置项
const currEditDialogOptions = ref<IFormOption[]>([]);
const currEditDialogSlotOptions = ref<IFormOption[]>([]);
// 当前编辑数据
const currEditData = ref({});
// 编辑弹窗类型
const currEditDialogType = ref<"edit" | "add">();

// 详情弹窗显隐
const showDetailDialog = ref(false);
// 当前详情数据
const currDetailData = ref<Record<string, unknown>>({});

/**
 * 详情弹窗
 */
const handleDetail = async () => {
  if (!props.rowData) {
    console.error("请传入行数据参数`rowData`");
    return;
  }
  // 拷贝行数据(响应式，用于钩子函数处理数据)
  const copyRowData = ref(deepCopy(props.rowData));
  // 先弹出弹窗（避免加载数据时不好的用户体验）
  showDetailDialog.value = true;
  // 给出loading
  dialogLoading.value = true;
  // 详情展示前钩子
  if (props.beforeShowDetailDialog) {
    await props.beforeShowDetailDialog(copyRowData, detailOptions);
  }
  // 经过处理的行数据赋值给最终展示的默认数据
  currDetailData.value = copyRowData.value;
  // 关闭loading
  dialogLoading.value = false;
};

/**
 * 编辑or新增
 */
const handleEdit = async (type: "edit" | "add") => {
  if (!props.rowData) {
    console.error("请传入行数据参数`rowData`");
    return;
  }
  // 缓存类型
  currEditDialogType.value = type;
  // 配置项
  currEditDialogOptions.value =
    type === "edit" ? editFormOptions.value : addFormOptions.value;
  // 弹窗固定参数
  currDialogProps.value = {
    ...(type === "edit" ? props.editDialogProps : props.addDialogProps),
  };
  // 表单插槽配置项
  currEditDialogSlotOptions.value = (
    type === "edit" ? editFormOptions.value : addFormOptions.value
  ).filter((opt: { slot?: string }) => opt.slot);
  // 拷贝行数据(响应式，用于钩子函数处理数据)
  const copyRowData = ref(deepCopy(props.rowData));
  // 先弹出弹窗（避免加载数据时不好的用户体验）
  showEditDialog.value = true;
  // 给出loading
  dialogLoading.value = true;
  // 编辑前钩子
  if (type === "edit" && props.beforeShowEditDialog) {
    await props.beforeShowEditDialog(copyRowData, editFormOptions);
  }
  // 新增前钩子
  if (type === "add" && props.beforeShowAddDialog) {
    await props.beforeShowAddDialog(copyRowData, addFormOptions);
  }
  // 经过处理的行数据赋值给最终展示的默认数据
  currEditData.value = copyRowData.value;
  // 关闭loading
  dialogLoading.value = false;
};

// 编辑完成确认
const handleEditSubmit = (
  formData: Record<string, unknown>,
  callbacks: SubmitCallbacks
) => emit("submit", formData, callbacks, currEditDialogType.value);

// 删除按钮事件
const handleDelete = () => {
  deleteLoading.value = true;
  emit("delete", deepCopy(props.rowData), {
    closeLoading() {
      deleteLoading.value = false;
    },
  });

  // 这里给出延时关闭loading,避免忘记执行closeLoading方法导致loading永久
  // 一般请求执行1.5秒也该关了
  const timer = setTimeout(() => {
    deleteLoading.value = false;
    clearTimeout(timer);
  }, 1500);
};

/**
 * 文件更新事件
 */
const bindUploadChange = (
  uploadParams: UploadEventParams,
  form: UploadChangeForm
) => {
  if (props.uploadChange) {
    props.uploadChange(uploadParams, form);
  }
};
</script>

<style lang="scss" scoped>
.row-btns {
  :deep(.fy-icon) {
    margin-right: 3px;
  }
}
</style>
